﻿using HybridCLR.Editor.Commands;
using HybridCLR.Editor.Settings;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEngine;

internal static class Link
{
    private static readonly HashSet<string> assemblyAlls = new HashSet<string>
    {
        "mscorlib",
    };

    // 环境不同时生成的link是不一样的
    // 又因为多出来的那一点代码并没有多大影响
    // 所以这里增加一个所有环境下都统一不裁剪的类列表
    private static readonly Dictionary<string, List<string>> fixedList = new Dictionary<string, List<string>>()
    {

    };

    [MenuItem("Tools/Link/GenerateLink")]
    public static void GenerateLink()
    {
        Generate(EditorUserBuildSettings.activeBuildTarget);
        CopyLink();
    }

    [MenuItem("Tools/Link/CheckLink")]
    public static void CheckBuildTargetLink()
    {
        Generate(EditorUserBuildSettings.activeBuildTarget);
        CheckLink();
    }

    public static void Merge(List<string> paths, string savePath)
    {
        Dictionary<string, (bool isAll, HashSet<string> typeList)> result = new Dictionary<string, (bool isAll, HashSet<string>)>();
        foreach (var path in paths)
        {
            if (File.Exists(path))
            {
                var list = ReadLink(path);
                foreach (var item in list)
                {
                    bool isAssembly = string.IsNullOrEmpty(item.assembly);
                    var assemblyName = isAssembly ? item.name : item.assembly;
                    if (result.TryGetValue(assemblyName, out var value))
                    {
                        if (isAssembly && item.isAll)
                            result[assemblyName] = (true, value.typeList);
                    }
                    else
                    {
                        value = (isAssembly ? item.isAll : false, new HashSet<string>());
                        result[assemblyName] = value;
                    }

                    if (!isAssembly && !value.typeList.Contains(item.name))
                    {
                        value.typeList.Add(item.name);
                    }
                }
            }
        }

        var dir = Path.GetDirectoryName(savePath);
        if (!Directory.Exists(dir))
            Directory.CreateDirectory(dir);

        List<(string assemblyName, List<string> refList)> xmlList = new List<(string assemblyName, List<string> refList)>();
        foreach (var item in result)
        {
            var typeList = new List<string>();
            xmlList.Add((item.Key, typeList));
            if (!item.Value.isAll)
            {
                foreach (var type in item.Value.typeList)
                {
                    typeList.Add(type);
                }
            }
        }
        GenerateLink(savePath, xmlList);
    }

    public static void CopyLink()
    {
        var linkPath = Application.dataPath + "/" + HybridCLRSettings.Instance.outputLinkFile;
        if (File.Exists(linkPath))
        {
            File.Copy(linkPath, Application.dataPath + "/" + "link.xml", true);
            AssetDatabase.Refresh();
        }
    }

    // 生成AssetBundle时需要检查类是否在整包被裁剪
    public static bool CheckLink()
    {
        if (!File.Exists(Application.dataPath + "/link.xml"))
            return true;

        var newLink = ReadLink(Application.dataPath + "/" + HybridCLRSettings.Instance.outputLinkFile);
        var link = ReadLink(Application.dataPath + "/" + "link.xml");

        for (int i = link.Count - 1; i >= 0; i--)
        {
            var index = newLink.IndexOf(link[i]);
            if (index != -1)
            {
                link.RemoveAt(i);
                newLink.RemoveAt(index);
            }
        }

        if (newLink.Count > 0)
        {
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var item in newLink)
            {
                if (string.IsNullOrEmpty(item.assembly))
                    stringBuilder.AppendLine($"link add : {item.name} [all]{item.isAll}");
                else
                    stringBuilder.AppendLine($"link add : {item.name}");
            }
            Debug.LogError($"link.xml 新增类型!\n如果该类型确实为必须类型请重新构建整包\n否则更改新增写法去除该类引用\n{stringBuilder}");
            return false;
        }
        Debug.Log("link check succeed!");
        return true;
    }

    public static void Generate(BuildTarget buildTarget)
    {
        LinkGeneratorCommand.GenerateLinkXml(buildTarget);
        var linkPath = Application.dataPath + "/" + HybridCLRSettings.Instance.outputLinkFile;
        if (!File.Exists(linkPath))
            return;

        XmlDocument doc = new XmlDocument();
        doc.Load(linkPath);
        var root = doc.DocumentElement;

        List<(string assemblyName, List<string> refList)> list = new List<(string assemblyName, List<string> refList)>();

        foreach (XmlNode assembly in root.ChildNodes)
        {
            var assemblyName = assembly.Attributes["fullname"].Value;
            var refList = new List<string>();
            if (!assemblyAlls.Contains(assemblyName))
            {
                foreach (XmlNode type in assembly.ChildNodes)
                {
                    refList.Add(type.Attributes["fullname"].Value);
                }
            }

            fixedList.TryGetValue(assemblyName, out var fixedTypeList);
            if (fixedTypeList != null)
            {
                foreach (var type in fixedTypeList)
                {
                    if (!refList.Contains(type))
                    {
                        refList.Add(type);
                    }
                }
            }

            list.Add((assemblyName, refList));
        }

        var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
        foreach (var assemblyName in assemblyAlls)
        {
            var res = list.Find(a => a.assemblyName == assemblyName);
            if (string.IsNullOrEmpty(res.assemblyName))
            {
                var assembly = assemblies.Find(a => a.GetName().Name == assemblyName);
                if (assembly != null)
                {
                    list.Add((assemblyName, new List<string>()));
                }
            }
        }

        foreach (var item in fixedList)
        {
            var res = list.Find(a => a.assemblyName == item.Key);
            if (string.IsNullOrEmpty(res.assemblyName))
            {
                var assembly = assemblies.Find(a => a.GetName().Name == item.Key);
                if (assembly != null)
                {
                    list.Add((item.Key, item.Value));
                }
            }
        }

        GenerateLink(Application.dataPath + "/" + HybridCLRSettings.Instance.outputLinkFile, list);
    }

    private static void GenerateLink(string savePath, List<(string assemblyName, List<string> refList)> list)
    {
        XmlWriter xmlWriter = XmlWriter.Create(savePath, new XmlWriterSettings
        {
            Encoding = Encoding.UTF8,
            Indent = true
        });
        xmlWriter.WriteStartDocument();
        xmlWriter.WriteStartElement("linker");
        list.Sort((a, b) => string.Compare(a.assemblyName, b.assemblyName));
        foreach (var item in list)
        {
            xmlWriter.WriteStartElement("assembly");
            xmlWriter.WriteAttributeString("fullname", item.assemblyName);
            if (item.refList.Count != 0)
            {
                item.refList.Sort(string.CompareOrdinal);
                foreach (var item2 in item.refList)
                {
                    xmlWriter.WriteStartElement("type");
                    xmlWriter.WriteAttributeString("fullname", item2);
                    xmlWriter.WriteAttributeString("preserve", "all");
                    xmlWriter.WriteEndElement();
                }
            }
            else
            {
                xmlWriter.WriteAttributeString("preserve", "all");
            }
            xmlWriter.WriteEndElement();
        }

        xmlWriter.WriteEndElement();
        xmlWriter.WriteEndDocument();
        xmlWriter.Close();
        AssetDatabase.Refresh();
        Debug.Log($"update : {savePath}");
    }

    private static List<(string name, string assembly, bool isAll)> ReadLink(string path)
    {
        List<(string name, string assembly, bool isAll)> list = new List<(string name, string assembly, bool isAll)>();
        if (!File.Exists(path))
            return list;
        XmlDocument doc = new XmlDocument();
        doc.Load(path);

        foreach (XmlNode assembly in doc.DocumentElement.ChildNodes)
        {
            list.Add((assembly.Attributes["fullname"].Value, string.Empty, assembly.ChildNodes.Count == 0));
            foreach (XmlNode type in assembly.ChildNodes)
            {
                list.Add((type.Attributes["fullname"].Value, assembly.Attributes["fullname"].Value, true));
            }
        }

        return list;
    }

}
